<?php
/**
 * Zhub - A PHP Framework For API Developer
 *
 * This file is part of the Zhub package.
 *
 * (c) YiLong Fan <fantiq@163.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @author fanyilong <fantiq@163.com>
 * @copyright 2017 fanyilong. All rights reserved.
 * @package Zhub
 * @date 18-2-11
 */

namespace Zhub\DataStruct;


use Zhub\DataStruct\Exceptions\StructPropertyObjectType;
use Zhub\DataStruct\Exceptions\StructPropertyTypeInvalid;
use Zhub\DataStruct\Maps\HashMap;

abstract class Struct
{
    private $map = null;

    abstract public static function initialize():array ;

    public function __construct(array $init = [], int $status = Map::READ_WRITE)
    {
        $prototype = $this->prepare($init);
        $this->map = new HashMap($prototype, $status);
    }

    protected function prepare(array $init = []):array
    {
        $prototype = static::initialize();
        foreach ($prototype as $property => $defaultValue) {
            // handle the callback property default value
            $defaultValueClass = is_callable($defaultValue) ? call_user_func($defaultValue) : null;
            // TODO 如果给定的初始值是 null 则使用默认值 [done]
            if (array_key_exists($property, $init) && $init[$property] !== null) {
                // 需要覆盖参数
                $newValue = $init[$property];
                // ======
                $newValueType = null;
                $defaultValueType = null;
                // get the default value type and the given value type
                if ($defaultValueClass) {
                    // handle the property value as object
                    if (!is_object($newValue)) {
                        throw new StructPropertyTypeInvalid($property, 'object', gettype($newValue));
                    }
                    $newValueType = get_class($newValue);
                    if (is_object($defaultValueClass)) {
                        $defaultValueType = get_class($defaultValueClass);
                    }
                    elseif (is_string($defaultValueClass) && class_exists($defaultValueClass)) {
                        $defaultValueType = $defaultValueClass;
                    }
                    else {
                        throw new StructPropertyObjectType($property, gettype($defaultValueClass));
                    }
                }
                else {
                    $newValueType = gettype($newValue);
                    $defaultValueType = gettype($defaultValue);
                }

                if ($newValueType != $defaultValueType) {
                    throw new StructPropertyTypeInvalid($property, $defaultValueType, $newValueType);
                }
                $prototype[$property] = $newValue;

                continue;
            }

            $initValue = null;

            if ($defaultValueClass) {
                if (is_object($defaultValueClass)) {
                    $initValue = $defaultValueClass;
                }
                elseif(is_string($defaultValueClass) && class_exists($defaultValueClass)) {
                    $initValue = new $defaultValueClass;
                }
                else {
                    throw new \Exception("which the default value type is callback must return a object ro a class name");
                }
            }
            else {
                $initValue = $defaultValue;
            }
            $prototype[$property] = $initValue;

        }
        return $prototype;
    }

    protected function get(string $key)
    {
        return $this->map->get($key);
    }
    protected function set(string $key, $val)
    {
        $this->map->put($key, $val);
    }

    public function toArray():array
    {
        return $this->map->toArray();
    }
    public function toString():string
    {
        return $this->map->toString();
    }
    public function __toString():string
    {
        return $this->toString();
    }

    public function __clone()
    {
        // 由于 map 是引用 这里处理深拷贝的情况
        $this->map = clone $this->map;
    }

    public function clear()
    {
        $this->__construct();
    }

}